package com.tina.services.imppl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tina.common.PathUtils;
import com.tina.common.SpringUtil;
import com.tina.entity.Chunk;
import com.tina.entity.Files;
import com.tina.mapper.FileMapper;
import com.tina.services.FileServiceInterface;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.FileUtils;

/**
 * @author tina
 * @since 2021-10-15 10:55
 */
@Service
public class FileService implements FileServiceInterface {

    @Autowired
    private FileMapper fileMapper;

    @Override
    public List<Files> getFileList() throws IOException {
        LambdaQueryWrapper<Files> wrapper = new LambdaQueryWrapper<Files>();
        return fileMapper.selectList(wrapper);
    }

    @Override
    public void fileDownload(String fileMd5, String fileName, HttpServletRequest request, HttpServletResponse response) {
        //得到文件信息
        LambdaQueryWrapper<Files> wrapper = new LambdaQueryWrapper<Files>();
        wrapper.eq(Files::getFileMd5, fileMd5);
        wrapper.eq(Files::getFileName, fileName);
        FileMapper bean = SpringUtil.getBean(FileMapper.class);
        Files files = bean.selectOne(wrapper);

        String fullPath = PathUtils.getFileDir() + files.getFileMd5();
        File downloadFile = new File(fullPath);

        // 设置响应内容类型
        response.setContentType(files.getFileName());

        // 响应头的文件信息
        String headerValue = null;
        try {
            //防止中文乱码！
            headerValue = String.format("attachment; filename=\"%s\"", java.net.URLEncoder.encode(files.getFileName(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        response.setHeader("Content-Disposition", headerValue);
        // 解析断点续传相关信息
        response.setHeader("Accept-Ranges", "bytes");
        long downloadSize = downloadFile.length();
        //开始位置，结束位置(0-0指全部)
        long fromPos = 0, toPos = 0;
        if (request.getHeader("Range") == null) {
            //设置文件总大小
            response.setHeader("Content-Length", downloadSize + "");
        } else {
            // 若客户端传来Range，说明之前下载了一部分，设置206状态(SC_PARTIAL_CONTENT)
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            String range = request.getHeader("Range");
            String bytes = range.replaceAll("bytes=", "");
            String[] ary = bytes.split("-");
            fromPos = Long.parseLong(ary[0]);
            if (ary.length == 2) {
                toPos = Long.parseLong(ary[1]);
            }
            int size;
            if (toPos > fromPos) {
                size = (int) (toPos - fromPos);
            } else {
                size = (int) (downloadSize - fromPos);
            }
            response.setHeader("Content-Length", String.valueOf(size));
            downloadSize = size;
        }
        try (
                RandomAccessFile in = new RandomAccessFile(downloadFile, "rw");
                OutputStream out = response.getOutputStream()
        ) {
            // 设置下载起始位置
            if (fromPos > 0) {
                in.seek(fromPos);
            }
            // 缓冲区大小
            int bufLen = (int) (downloadSize < 2048 ? downloadSize : 2048);
            byte[] buffer = new byte[bufLen];
            int num;
            // 当前写到客户端的大小
            int count = 0;
            while ((num = in.read(buffer)) > 0) {
                out.write(buffer, 0, num);
                count += num;
                //处理最后一段，计算不满缓冲区的大小
                if (downloadSize - count < bufLen) {
                    bufLen = (int) (downloadSize - count);
                    if (bufLen == 0) {
                        break;
                    }
                    buffer = new byte[bufLen];
                }
            }
            response.flushBuffer();
        } catch (IOException e) {
            System.out.println("用户停止了下载!");
        }
    }

    /**
     * 每一个上传块都会包含如下分块信息：
     * chunkNumber: 当前块的次序，第一个块是 1，注意不是从 0 开始的。
     * totalChunks: 文件被分成块的总数。
     * chunkSize: 分块大小，根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
     * currentChunkSize: 当前块的大小，实际大小。
     * totalSize: 文件总大小。
     * identifier: 这个就是每个文件的唯一标示。
     * filename: 文件名。
     * relativePath: 文件夹上传的时候文件的相对路径属性。
     * 一个分块可以被上传多次，当然这肯定不是标准行为，但是在实际上传过程中是可能发生这种事情的，这种重传也是本库的特性之一。
     * <p>
     * 根据响应码认为成功或失败的：
     * 200 文件上传完成
     * 201 文加快上传成功
     * 500 第一块上传失败，取消整个文件上传
     * 507 服务器出错自动重试该文件块上传
     */
    @Override
    public String fileUploadPost(Chunk chunk, HttpServletResponse response) {
        File file = new File(PathUtils.getFileDir(), chunk.getFilename());
        //第一个块,则新建文件
        if (chunk.getChunkNumber() == 1 && !file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                response.setStatus(500);
                return "exception:createFileException";
            }
        }

        //进行写文件操作
        try (
                //将块文件写入文件中
                InputStream fos = chunk.getFile().getInputStream();
                RandomAccessFile raf = new RandomAccessFile(file, "rw")
        ) {
            int len = -1;
            byte[] buffer = new byte[1024];
            raf.seek((chunk.getChunkNumber() - 1) * 1024 * 1024);
            while ((len = fos.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
            if (chunk.getChunkNumber() == 1) {
                file.delete();
            }
            response.setStatus(507);
            return "exception:writeFileException";
        }
        if (chunk.getChunkNumber().equals(chunk.getTotalChunks())) {
            response.setStatus(200);
            if (!"".equals(chunk.getFilename())) {
                installFile(chunk);
            }
            return "over";
        } else {
            response.setStatus(201);
            return "ok";
        }
    }

    /**
     * 根据响应码认为成功或失败的：
     * 200 文件上传完成
     * 201 文加快上传成功
     * 500 第一块上传失败，取消整个文件上传
     * 507 服务器出错自动重试该文件块上传
     *
     * @param chunk
     * @param response
     * @return
     */
    public String fileUploadPost1(Chunk chunk, HttpServletResponse response) {
        String fileDir = PathUtils.getFileDir();
        String key = chunk.getIdentifier();
        MultipartFile file = chunk.getFile();

        // 保存文件到本地
        String path = new StringBuffer()
                .append("/")
                .append(chunk.getIdentifier())
                .toString();

        String localPath = new StringBuffer(path)
                .append(".")
                .append(chunk.getChunkNumber())
                .toString();

        //上传文件包含分片信息
        String fullPath = fileDir + localPath;
        File dest = new File(fullPath);

        if (dest.exists()) {
            System.out.println("--------------------------------" + dest.exists());
            response.setStatus(201);
            return "分片存在";
        }

        //进行上传
        try {
            FileUtils.copyInputStreamToFile(file.getInputStream(), dest);
            response.setStatus(201);
        } catch (IOException e) {
            e.printStackTrace();
            response.setStatus(507);
            return e.getMessage();
        }

        if (chunk.getChunkNumber().equals(chunk.getTotalChunks())) {
            chunk.setPath(fileDir + path);
            merge(chunk);
            response.setStatus(200);
        }

        return "OK";
    }

    /**
     * 文件合并
     *
     * @throws Exception
     */
    public void merge(Chunk chunk) {
        String path = chunk.getPath();
        Integer shardTotal = chunk.getTotalChunks();
        File newFile = new File(path);
        FileOutputStream outputStream = null;
        FileInputStream fileInputStream = null;//分片文件
        byte[] byt = new byte[10 * 1024 * 1024];
        int len;

        try {
            outputStream = new FileOutputStream(newFile, true);//文件追加写入
            for (int i = 0; i < shardTotal; i++) {
                // 读取第i个分片
                fileInputStream = new FileInputStream(new java.io.File(path + "." + (i + 1))); //  course\6sfSqfOwzmik4A4icMYuUe.mp4.1
                while ((len = fileInputStream.read(byt)) != -1) {
                    outputStream.write(byt, 0, len);
                }
            }

            installFile(chunk);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        try {
            //进行删除文件前，需要gc回收
            System.gc();
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 删除分片
        for (int i = 0; i < shardTotal; i++) {
            String filePath = path + "." + (i + 1);
            File file = new File(filePath);
            file.delete();
        }
    }


    private Files installFile(Chunk chunk) {
        // 新增数据
        Files files = new Files();
        BeanUtils.copyProperties(chunk, files);
        files.setFileMd5(chunk.getIdentifier());
        files.setFileType(chunk.getFilename().substring(chunk.getFilename().lastIndexOf(".") + 1));
        files.setFileName(chunk.getFilename());
        files.setCreateTime(new Date());
        files.setFileSize(chunk.getTotalSize());
        fileMapper.insert(files);
        return files;
    }

    /**
     * 校验文件
     *
     * @param chunk 文件接收参数
     * @return boolean
     */
    @Override
    public boolean fileUploadGet(Chunk chunk) {
        return install(chunk);
    }

    private boolean install(Chunk chunk) {
        LambdaQueryWrapper<Files> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Files::getFileMd5, chunk.getIdentifier());
        Integer integer = fileMapper.selectCount(wrapper);
        if (integer > 0) {
            wrapper.eq(Files::getFileName, chunk.getFilename());
            Files files = fileMapper.selectOne(wrapper);
            if (files == null) {
                installFile(chunk);
                return true;
            } else {
                return true;
            }
        }
        return false;
    }

    public void page(){
        Page page=new Page();


        IPage<Files> filesIPage = fileMapper.selectPage(null, null);
        System.out.println(filesIPage);
    }

}
